--[[ 我自己的mame脚本库,目前做了带有错误处理和防暂停是cpu飙升bug的每帧绑定函数:
  runPerFrame和debugRunPerFrame
  还有自定义热键的功能!setHotkey函数!每帧检测按键状态兵决定是否执行热键函数
  增加数据监视函数

  By setycyas @2025-02-18
]]

local GLOBAL_FLAG = 'MyMame_Global' -- 模块标记名称,防重加载,导出到变量_G时的索引
-- 防重加载,返回nil时表示以加载过
if _G[GLOBAL_FLAG] then
  print("MyMame already loaded!")
  return nil
end

-- 本模块
local MyMame = {
  gameName = '', -- 游戏名称
  errorFlag = false, -- 运行出错标记
  gFrameFlag = 'MyMame frameFlag', -- 导出到_G中,如果存在且为true表示每帧运行已加载,防重复
  --[[ 自定义的热键表,重要!关系到一键宏!键为keycode,值为{seq, state, func},表示轮询队列,状态,
    绑定函数,状态放开为0,按下为1或其他大于0的值
  ]]
  hotkeys = {},
  cpuIndex = ':maincpu', --获取cpu=manager.machine.devices[cpuIndex]
  memIndex = 'program', --获取内存mem=cpu.spaces[memIndex]
  screenIndex = ':screen', --获取screen=manager.machine.screens[screenIndex],也可能':screen.0'
  -- 主要的mame api对象
  cpu = nil,
  screen = nil,
  mem = nil,
  container = nil,
  ui_container = nil,
}

-- 刷新mame api对象,如果获取失败返回false
function MyMame.refreshDevices()
  MyMame.cpu = manager.machine.devices[MyMame.cpuIndex]
  MyMame.screen = manager.machine.screens[MyMame.screenIndex]
  if ((not MyMame.cpu) or (not MyMame.screen)) then
    print('failed to get devices')
    return false
  end
  MyMame.mem = MyMame.cpu.spaces[MyMame.memIndex]
  MyMame.container = MyMame.screen.container
  MyMame.ui_container = manager.machine.render.ui_container
  if ((not MyMame.mem) or (not MyMame.container) or (not MyMame.ui_container)) then
    print('failed to get devices')
    return false
  end
  --print('get devices')
  return true
end

-- 绑定每帧运行函数,f是函数,gameName是游戏名称字符串,用于显示
function MyMame.runPerFrame(f, gameName)
  local myFun -- 真正绑定的函数,先运行一些过滤再运行f()
  
  if _G[MyMame.gFrameFlag] then
    print("perFrame function already loaded!")
    return
  else
    myFun = function()
      -- 暂停过滤
      if (manager.machine.paused) then
        return
      end
      -- 获取设备出错
      if (not MyMame.refreshDevices()) then
        return
      end
      -- 出错一次过滤
      if (MyMame.errorFlag) then
        return
      end
      -- 处理热键
      MyMame._handleHotkeys()
      -- 执行f
      f()
    end
    -- 绑定每帧运行函数
    emu.register_frame_done(myFun, "frame")
    _G[MyMame.gFrameFlag] = true
    print("runPerFrame script load: "..gameName)
  end
  
end

-- 以调试模式绑定每帧运行函数,f是函数,gameName是游戏名称字符串,用于显示,效率相对低测试时用
function MyMame.debugRunPerFrame(f, gameName)
  local myFun -- 真正绑定的函数,先运行一些过滤再运行f()
  local success, err -- 调试用pcall
  
  if _G[MyMame.gFrameFlag] then
    print("perFrame function already loaded!")
    return
  else
    myFun = function()
      -- 暂停过滤,目前debug状态不过滤暂停因为不利于逐帧测试,但暂停就有风险!
      if (manager.machine.paused) then
        --return
      end
      -- 获取设备出错
      if (not MyMame.refreshDevices()) then
        return
      end
      -- 出错一次过滤
      if (MyMame.errorFlag) then
        return
      end
      -- 处理热键
      MyMame._handleHotkeys()
      -- 调试执行f
      success, err = pcall(f)
      if not success then
        print("Catch ERROR at "..MyMame.screen:frame_number().." frame:", err)
        MyMame.errorFlag = true
      end
    end
    -- 绑定每帧运行函数
    emu.register_frame_done(myFun, "frame")
    _G[MyMame.gFrameFlag] = true
    print("debugRunPerFrame script load: "..gameName)
  end
  
end

-- 设定热键与执行的函数,热键以"KEYCODE_X",具体参见inputmacro插件的记录文件
function MyMame.setHotkey(keycode, f)
  -- 已经设定的情况
  if (MyMame.hotkeys[keycode]) then
    print(keycode.." already set!")
    return
  end
  MyMame.hotkeys[keycode] = {
    seq = manager.machine.input:seq_from_tokens(keycode),
    state = 0,
    func = f
  }
  return
end

-- 屏幕从上到下显示一个数组,用于监视数据的实时变化
function MyMame.showInfo(array, color)
  local ui_container
  local index, value
  
  ui_container = manager.machine.render.ui_container
  --默认颜色为纯白
  if color == nil then
    color = 0xFFffffff
  end
  
  for index, value in ipairs(array) do
    ui_container:draw_text(0.05, 0.05*index, value, color)
  end
  
end

-- addrArr是一个记录了地址的数组,读取它们的数值并返回一个字符串数组,字符串为:
-- 地址=数值,方便显示.addrName是一个可以省略的地址名称表,如果没有则表示不使用别名
-- addrType是地址类型表,指定特定地址的类型,省略是u8,也可以用u16,i8,i16
-- addrFormat同样,是在string.format中的类型参数,省略为%d
function MyMame.readAddrArr(addrArr, addrName, addrType, addrFormat)
  local result
  local mem
  local i, addr
  local str, value, name, readType, strForamt
  result = {}
  mem = manager.machine.devices[MyMame.cpuIndex].spaces[MyMame.memIndex]
  
  for i, addr in ipairs(addrArr) do
    -- 获取名称,如未指定则用地址代替
    if (addrName and (addrName[addr])) then
      name = addrName[addr]
    else
      name = string.format("0x%08x", addr)
    end
    -- 获取读取方法,如未指定则用read_u8
    readType = 'u8'
    if (addrType and (addrType[addr])) then
      if (string.lower(addrType[addr]) == 'u16') then
        readType = 'u16'
      elseif (string.lower(addrType[addr]) == 'i16') then
        readType = 'i16'
      elseif (string.lower(addrType[addr]) == 'i8') then
        readType = 'i8'  
      else
        readType = 'u8'
      end
    end
    -- 读取数据
    if (readType == 'u16') then
      value = mem:read_u16(addr)
    elseif (readType == 'i16') then 
      value = mem:read_i16(addr)
    elseif (readType == 'i8') then 
      value = mem:read_i8(addr)
    else
      value = mem:read_u8(addr)
    end
    -- 连接形成字符串
    if (addrFormat) and (addrFormat[addr]) then
      strFormat = addrFormat[addr]
    else
      strFormat = "%d"
    end
    str = string.format("%s = " .. strFormat, name, value)
    result[i] = str
  end
  
  return result
end

--[[私有函数]]
-- 每帧更新热键并运行按下的热键
function MyMame._handleHotkeys()
  local oldState
  local keycode, t
  -- 记录旧状态,更新后决定是否运行
  for keycode, t in pairs(MyMame.hotkeys) do
    oldState = t.state -- 旧状态
    -- 更新状态
    if (manager.machine.input:seq_pressed(t.seq)) then
      t.state = 1
    else
      t.state = 0
    end
    -- 判断是否执行热键
    if ((oldState == 0) and (t.state == 1)) then
      t.func()
    end
  end
end

-- 刷新mame api,导出全局变量
MyMame.refreshDevices()
_G[GLOBAL_FLAG] = MyMame
return MyMame
